package utils

import (
	"crypto/md5"
	"crypto/tls"
	"fmt"
	"gin-gorm-oj/model"
	"log"
	"math/rand"
	"net/smtp"
	"os"
	"strconv"
	"strings"
	"time"

	"gitee.com/zht639/my_gopkg/autosql"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"github.com/jordan-wright/email"
	"github.com/redis/go-redis/v9"
	"gorm.io/gorm"
)

var (
	DB          = InitMysql()
	RDB         = InitRedis()
	DefaultPage = "1"
	DefaultSize = "20"
	DateLayout  = "2006-01-02 15:04:05"
	myKey       = []byte("gin-gorm-oj-key")
	MyEmail     = "你的邮箱"
	SMTPServer  = "smtp." + strings.Split(MyEmail, "@")[1]
	AuthCode    = "你的授权码" //获取方式见README
)

// UserClaims 用于验证用户登录的结构体
type UserClaims struct {
	ID      int32  `json:"id"`
	Name    string `json:"name"`
	IsAdmin int32  `json:"is_admin"`
	jwt.StandardClaims
}

// ProblemPost 用于提交问题表单的结构体
type ProblemPost struct {
	ID         int32  `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
	Title      string `gorm:"column:title;comment:问题标题" json:"title"`               // 问题标题
	Content    string `gorm:"column:content;comment:问题内容" json:"content"`           // 问题内容
	MaxRuntime int32  `gorm:"column:max_runtime;comment:最大运行时间" json:"max_runtime"` // 最大运行时间
	MaxMem     int32  `gorm:"column:max_mem;comment:最大运行内存" json:"max_mem"`         // 最大运行内存
	CategoryID int32  `gorm:"column:category_id;not null" json:"category_id"`
}

// InitMysql 初始化数据库连接
func InitMysql() *gorm.DB {
	db, err := autosql.ConnMysql("root", "123456", "localhost", "3306", "db01")
	if err != nil {
		fmt.Println(err)
	}
	return db
}

// InitRedis 初始化Redis连接
func InitRedis() *redis.Client {
	return redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // no password set
		DB:       0,  // use default DB
	})
}

// GetProblems 获取问题列表
func GetProblems(keyword, categoryID string) *gorm.DB {
	tx := DB.Model(new(model.Problem)).
		Preload("Category"). // 预加载分类
		Where("problem.title LIKE ? OR problem.content LIKE ?", "%"+keyword+"%", "%"+keyword+"%")

	if categoryID != "" {
		tx = tx.Where("problem.category_id = ?", categoryID) // 分类过滤
	}

	return tx.Order("problem.id DESC")
}

// GetSubmits 获取提交列表
func GetSubmits(problemId, userId string, status int) *gorm.DB {
	tx := DB.Model(new(model.Submit)).
		Preload("Problem", func(db *gorm.DB) *gorm.DB {
			return db.Omit("content")
		}).Preload("User", func(db *gorm.DB) *gorm.DB {
		return db.Omit("password")
	})
	if problemId != "" {
		tx.Where("problem_id = ? ", problemId)
	}
	if userId != "" {
		tx.Where("user_id = ? ", userId)
	}
	if status != 0 {
		tx.Where("status = ? ", status)
	}
	return tx.Order("submit.id DESC")
}

// GetSizeAndPage 计算分页参数并返回
func GetSizeAndPage(c *gin.Context) (int, int) {
	// 从查询参数中获取每页记录数，默认为 DefaultSize
	size, _ := strconv.Atoi(c.DefaultQuery("size", DefaultSize))

	// 从查询参数中获取页码，默认为 DefaultPage，并进行类型转换
	page, err := strconv.Atoi(c.DefaultQuery("page", DefaultPage))
	if err != nil {
		log.Println("GetProblemList Page strconv Error:", err)
		return -1, 0
	}

	// 计算当前页的偏移量，分页逻辑
	page = (page - 1) * size

	return size, page
}

// GetMd5 获取字符串的 MD5 值
func GetMd5(s string) string {
	return fmt.Sprintf("%x", md5.Sum([]byte(s)))
}

// GenerateToken 生成 JWT token
func GenerateToken(id int32, name string, isAdmin int32) (string, error) {
	UserClaim := &UserClaims{
		ID:             id,
		Name:           name,
		IsAdmin:        isAdmin,
		StandardClaims: jwt.StandardClaims{}, // 可选标准声明字段（如过期时间等）
	}
	// 使用 HS256 签名算法生成 JWT token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim)
	// 使用私钥（myKey）对 token 进行签名，生成最终的 token 字符串
	tokenString, err := token.SignedString(myKey)
	if err != nil {
		return "", err
	}
	// 返回生成的 token 字符串
	return tokenString, nil
}

// AnalyseToken 解析 JWT token
func AnalyseToken(tokenString string) (*UserClaims, error) {
	userClaim := new(UserClaims)
	// 调用 jwt.ParseWithClaims 解析 token，使用 userClaim 接收解析结果
	claims, err := jwt.ParseWithClaims(tokenString, userClaim,
		// 提供回调函数，用于返回用于验证签名的密钥
		func(token *jwt.Token) (interface{}, error) {
			return myKey, nil
		})
	// 如果解析失败或其他错误，返回错误
	if err != nil {
		return nil, err
	}
	// 检查 token 是否有效，如果无效返回错误
	if !claims.Valid {
		return nil, fmt.Errorf("analyse Token Error:%v", err)
	}
	// 解析成功，返回用户声明
	return userClaim, nil
}

// GetRand
// 生成6位随机验证码
func GetRand() string {
	//Go 1.20 及以后版本中，默认情况下调用 rand.Intn 会自动使用一个随机种子，
	var s strings.Builder // 使用 strings.Builder 来优化字符串拼接
	for i := 0; i < 6; i++ {
		s.WriteString(strconv.Itoa(rand.Intn(10))) // 生成 0 到 9 的随机数并拼接成字符串
	}
	return s.String() // 返回拼接后的验证码
}

// SendCode
// 发送验证码
func SendCode(toUserEmail, code string) error {
	e := email.NewEmail() //创建并初始化邮件。
	e.From = "Get <" + MyEmail + ">"
	e.To = []string{toUserEmail}
	e.Subject = "验证码已发送，请查收"
	e.HTML = []byte("您的验证码：<b>" + code + "</b>")
	// 发送邮件，使用 TLS 加密连接
	return e.SendWithTLS(SMTPServer+":465",
		/*
			465这个端口用于 SSL（安全套接字层）的加密传输。也称为 SMTPS（安全 SMTP），
			是在邮件发送时对数据进行加密，确保数据在网络传输中是安全的。
		*/
		// 设置发件邮箱的用户名和授权码
		smtp.PlainAuth("", MyEmail, AuthCode, SMTPServer),
		&tls.Config{InsecureSkipVerify: true, ServerName: SMTPServer})
}

// CodeSave 保存代码到指定路径并返回代码文件的路径
func CodeSave(code []byte, id int32, name string) (string, error) {
	// 获取当前时间的时间戳，并格式化为字符串
	timeStamp := time.Now().Format("20060102150405") // 格式为：年-月-日-小时-分钟-秒

	// 生成一个唯一的目录名，用于存放代码文件
	dirName := "code/id" + strconv.Itoa(int(id)) + "_" + name + "_" + timeStamp
	// 设置代码文件的完整路径，包括文件名
	path := dirName + "/main.go"

	// 创建目录，设置权限为 0777（可读、可写、可执行）
	err := os.Mkdir(dirName, 0777)
	if err != nil {
		return "", err
	}

	// 创建一个新的代码文件
	f, err := os.Create(path)
	if err != nil {
		return "", err
	}
	defer f.Close() // 延迟关闭文件

	// 将代码内容写入文件
	_, err = f.Write(code)
	if err != nil {
		return "", err
	}

	return path, nil
}
